from django.db import models
from django.contrib.auth.models import User

from isbn import isValidISBN10, isValidISBN13, convertISBN10toISBN13
from isbn import InvalidISBN, filterDigits

class Creator(models.Model):
    u'''Author, illustrator, editor etc.'''
    name = models.CharField(max_length=128)
    meta_created = models.DateTimeField(auto_now_add=True)
    meta_changed = models.DateTimeField(auto_now=True)
    
    def __unicode__(self):
        return self.name

class Edition(models.Model):
    u'''A specific editon of a book, usually identified by ISBN.
    
    >>> alice = Edition(title=u'Annotated Alice')
    >>> alice.title
    u'Annotated Alice'
    >>> alice.save()
    >>> alice.creator_names
    u''
    >>> for name in [u'Lewis Carroll', u'John Tenniel', u'Martin Gardner']:
    ...   alice.add_creator_by_name(name)
    >>> alice.creator_names
    u'Lewis Carroll, John Tenniel, Martin Gardner'
    >>> for credit in alice.credit_set.all():
    ...   print credit
    Lewis Carroll: Annotated Alice
    John Tenniel: Annotated Alice
    Martin Gardner: Annotated Alice
    
    '''
    
    #XXX isbn should be unique, but there's no way to enter null via admin
    isbn = models.CharField(max_length=13, null=True, blank=True, db_index=True) 
    title = models.CharField(max_length=256, blank=True, db_index=True)
    creators = models.ManyToManyField(Creator, through='Credit', blank=True,
                                      null=True)
    meta_created = models.DateTimeField(auto_now_add=True)
    meta_changed = models.DateTimeField(auto_now=True)
        
    @property
    def creator_names(self):
        names = (unicode(c) for c in self.creators.all().order_by())
        return u', '.join(names)
    
    def __unicode__(self):
        return self.title or self.isbn or ('#%s' % self.id)

    def add_creator(self, creator, role='', citation_order=999):
        Credit.objects.create(creator = creator, edition=self, role=role,
               citation_order=citation_order)
    
    def add_creator_by_name(self, name='', role='', citation_order=999):
        creator = Creator(name=name)
        creator.save()
        self.add_creator(creator, role, citation_order)
    
    @classmethod    
    def get_or_create(klass, isbn='', title=''):
        u'''Get edition by ISBN, otherwise get it by Title. If not successful
            either way, create a new edition.
            
        XXX: this method must get the creator names as well, because:
            1) if edition is identified only by title, creators must be
                checked, because there can be two books with the same title
            2) if the edition is to be created, this method must create it
                with the creators and credits

        >>> isbn13 = '9781112223334'
        >>> book = Edition(isbn=isbn13, title=u'Stories')
        >>> book.save()
        >>> bool(book.id)
        True
        >>> Edition.get_or_create(isbn=isbn13)
        (<Edition: Stories>, False)
        >>> Edition.get_or_create()
        Traceback (most recent call last):
        ...
        TypeError: Either the isbn or the title must be given

        '''
        if not (isbn or title):
            raise TypeError, u'Either the isbn or the title must be given'
        created = False
        edition = None
        if isbn:
            isbn = filterDigits(isbn)
            if len(isbn) == 10:
                if isValidISBN10(isbn):
                    isbn = convertISBN10toISBN13(isbn)
                else:
                    raise InvalidISBN, isbn
            elif len(isbn) == 13:
                if not isValidISBN13(isbn):
                    raise InvalidISBN, isbn
            try:
                edition = klass.objects.get(isbn=isbn)
            except klass.DoesNotExist:
                pass
        if edition is None:
            try:
                # XXX: this may raise MultipleObjectsReturned
                edition = klass.objects.get(title=title)
            except klass.DoesNotExist:
                edition = klass.objects.create(isbn=isbn, title=title)
                created = True
        return (edition, created)

class Credit(models.Model):
    u'''Attribution'''
    
    creator = models.ForeignKey(Creator)
    edition = models.ForeignKey(Edition)
    role = models.CharField(max_length=32, blank=True)
    citation_order = models.PositiveSmallIntegerField(default=999)
    meta_created = models.DateTimeField(auto_now_add=True)
    meta_changed = models.DateTimeField(auto_now=True)
    
    def __unicode__(self):
        return u'%s: %s' % (self.creator, self.edition)
    
    class Meta:
        ordering = ['citation_order']

def add_edition(edition_dict):
    u'''add Editon, Creators and Credits from dict representation of edition
    
    >>> edict = dict(
    ...     title='Odissey',
    ...     isbn='123-456-789-x',
    ...     creators=[
    ...         {'name':'Homer', 'role':'author', 'citation_order':2},
    ...         {'name':'Bart', 'role':'illustrator', 'citation_order':1}
    ...     ]
    ... )
    >>> edition, new, creators = add_edition(edict)
    >>> print edition, new, creators
    Odissey True 2
    >>> print edition.isbn # converted to isbn13
    9781234567897
    >>> for credit in edition.credit_set.all():
    ...     print credit.citation_order, credit.creator.name
    1 Bart
    2 Homer
    
    This test does not pass... how to get the creators ordered by
    credit.citation_order?
    #>>> print edition.creator_names
    #Bart, Homer
    '''
    
    
    # XXX: needs refactoring, see Edition.get_or_create above
    new_edition = 0
    new_creators = 0
    edition, new = Edition.get_or_create(edition_dict.get('isbn'),
                                         edition_dict.get('title'))
    if new:
        new_edition = True
        for creator_dict in edition_dict.get('creators', []):
            creator, new = Creator.objects.get_or_create(name=creator_dict['name'])
            if new:
                new_creators += 1
            edition.add_creator(creator, creator_dict['role'], creator_dict['citation_order'])
    return (edition, new_edition, new_creators)


